#define WIN32_LEAN_AND_MEAN
#include <Windows.h>

#include "tdp2_vfs.hpp"


namespace TDP
{
	namespace OS
	{
		using namespace RDX;
		using namespace RDX::ObjectManagement;
		using namespace RDX::Programmability;
		using namespace RDX::Threading;

		class OSFileStream : public IO::IFileStream
		{
			Allocator _alloc;
			HANDLE _file;
			OSFileSystem *_ownerSys;
			bool _hasAborted;

		public:
			OSFileStream *prev, *next;

			OSFileStream(const Allocator &alloc, OSFileSystem *fsys, HANDLE file);

			LargeInt WriteBytes(const void *src, LargeInt numBytes);
			LargeInt ReadBytes(void *dest, LargeInt numBytes);
			void Seek(LargeInt offset, SeekType seekType);
			LargeInt Tell();
			void Close();
			void Abort();
			bool HasAborted() const;
		};

		class OSFileScanState : public IO::IFileScanState
		{
			WIN32_FIND_DATAW _findData;
			HANDLE _hfind;
			Char _filter[MAX_FILE_PATH];
			LargeInt _vextLen;
			bool _valid;
			RDX::ObjectManagement::Allocator _alloc;
			OSFileSystem *_ownerSys;

		public:
			OSFileScanState *prev, *next;

			OSFileScanState(OSFileSystem *ownerSys, const Char *rootDir, const Char *path, const Char *wildcard, const Char *vext, const RDX::ObjectManagement::Allocator &alloc);
			CRef<const String> OSFileScanState::NextFile(OperationContext *ctx, IObjectManager *objm);
			void Close();
		};

		OSFileScanState::OSFileScanState(OSFileSystem *ownerSys, const Char *rootDir, const Char *path, const Char *wildcard, const Char *vext, const RDX::ObjectManagement::Allocator &alloc)
		{
			size_t lens[5] = { 0, 0, 0, 0, 0 };
			const Char *strs[5] = { rootDir, RDX_STATIC_STRING("/"), path, wildcard, vext };

			_valid = false;
			_ownerSys = ownerSys;
			_hfind = NULL;
			_alloc = alloc;
			_vextLen = 0;
			if(vext)
				_vextLen = wcslen(vext);

			size_t totalLen = 0;
			for(int i=0;i<sizeof(strs) / sizeof(strs[0]);i++)
			{
				if(!strs[i])
					continue;
				size_t len = DynString<Char>::CStrLen(strs[i]);
				if(MAX_FILE_PATH - 1 - totalLen < len)
					return;
				const Char *str = strs[i];
				while(*str)
					_filter[totalLen++] = *str++;
			}
			_filter[totalLen] = static_cast<Char>(0);
			_valid = true;
		}

		void OSFileScanState::Close()
		{
			if(_hfind)
				FindClose(_hfind);

			this->_ownerSys->UnlinkScanState(this);

			this->~OSFileScanState();
			_alloc.Free(this);
		}

		CRef<const String> OSFileScanState::NextFile(OperationContext *ctx, IObjectManager *objm)
		{
			if(!_valid)
				return NULL;

			while(true)
			{
				if(!_hfind)
				{
					_hfind = FindFirstFileW(_filter, &_findData);
					if(_hfind == INVALID_HANDLE_VALUE)
					{
						_hfind = NULL;
						return NULL;
					}
				}
				else
				{
					if(!FindNextFileW(_hfind, &_findData))
						return NULL;
				}
				if((_findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0)
					break;
			}

			return objm->CreateString(ctx, _findData.cFileName, wcslen(_findData.cFileName) - _vextLen);
		}

		OSFileSystem::OSFileSystem(const PanicHandler *ph, const Char *workingDirectory, const RDX::ObjectManagement::Allocator &alloc)
			: _workingDirectory(alloc)
		{
			_alloc = alloc;
			_firstScanState = NULL;
			_firstFile = NULL;
			_workingDirectory.Set(ph, workingDirectory);
		}

		OSFileSystem::~OSFileSystem()
		{
			OSFileStream *stream = static_cast<OSFileStream *>(_firstFile);
			while(stream)
			{
				OSFileStream *nextStream = stream->next;
				stream->Close();
				stream = nextStream;
			}

			OSFileScanState *scanState = static_cast<OSFileScanState *>(_firstScanState);
			while(scanState)
			{
				OSFileScanState *nextState = scanState->next;
				scanState->Close();
				scanState = nextState;
			}
		}

		RDX::IO::IFileStream *OSFileSystem::Open(const Char *path, bool write)
		{
			Char expandedPath[MAX_FILE_PATH];
			DWORD access = write ? GENERIC_WRITE : GENERIC_READ;
			DWORD sharing = write ? 0 : FILE_SHARE_READ;
			DWORD creation = write ? OPEN_ALWAYS : OPEN_EXISTING;

			OSFileStream *stream = _alloc.CAlloc<OSFileStream>(1);
			if(!stream)
				return NULL;

			if(1 + wcslen(this->_workingDirectory.Chars()) + wcslen(L"\\") + wcslen(path) >= MAX_FILE_PATH)
				return NULL;
			wcscpy(expandedPath, this->_workingDirectory.Chars());
			wcscat(expandedPath, L"\\");
			wcscat(expandedPath, path);

			HANDLE f = CreateFileW(expandedPath, access, sharing, NULL, creation, FILE_ATTRIBUTE_NORMAL, NULL);
			if(f == INVALID_HANDLE_VALUE)
			{
				_alloc.Free(stream);
				return NULL;
			}

			new (stream) OSFileStream(_alloc, this, f);

			{
				MutexLock<LightMutex> _(&_mutex);
				OSFileStream *firstOSFile = static_cast<OSFileStream *>(_firstFile);
				if(firstOSFile)
					firstOSFile->prev = stream;
				stream->next = firstOSFile;
				stream->prev = NULL;
				_firstFile = stream;
			}

			return stream;
		}

		RDX::IO::IFileScanState *OSFileSystem::ScanDirectory(const Char *directory, const Char *wildcard, const Char *vext)
		{
			OSFileScanState *scanState = _alloc.CAlloc<OSFileScanState>(1);
			if(!scanState)
				return NULL;

			new (scanState) OSFileScanState(this, this->_workingDirectory.Chars(), directory, wildcard, vext, _alloc);

			{
				MutexLock<LightMutex> _(&_mutex);
				OSFileScanState *firstOSScan = static_cast<OSFileScanState *>(_firstScanState);
				if(firstOSScan)
					firstOSScan->prev = scanState;
				scanState->next = firstOSScan;
				scanState->prev = NULL;
				_firstScanState = scanState;
			}

			return scanState;
		}

		void OSFileSystem::UnlinkScanState(RDX::IO::IFileScanState *scanState)
		{
			MutexLock<LightMutex> _(&_mutex);
			OSFileScanState *osScan = static_cast<OSFileScanState *>(scanState);

			if(osScan->prev)
				osScan->prev->next = osScan->next;
			if(osScan->next)
				osScan->next->prev = osScan->prev;
			
			if(_firstScanState == osScan)
				_firstScanState = osScan->next;
			osScan->prev = osScan->next = NULL;
		}

		void OSFileSystem::UnlinkStream(RDX::IO::IFileStream *stream)
		{
			MutexLock<LightMutex> _(&_mutex);
			OSFileStream *osStream = static_cast<OSFileStream *>(stream);

			if(osStream->prev)
				osStream->prev->next = osStream->next;
			if(osStream->next)
				osStream->next->prev = osStream->prev;
			
			if(_firstFile == osStream)
				_firstFile = osStream->next;
			osStream->prev = osStream->next = NULL;
		}

		// ==============================================================================
		OSFileStream::OSFileStream(const Allocator &alloc, OSFileSystem *fsys, HANDLE file)
		{
			_alloc = alloc;
			_file = file;
			_ownerSys = fsys;
			_hasAborted = false;
		}

		void OSFileStream::Abort()
		{
			_hasAborted = true;
		}

		bool OSFileStream::HasAborted() const
		{
			return _hasAborted;
		}

		LargeInt OSFileStream::WriteBytes(const void *src, LargeInt numBytes)
		{
			DWORD bytesWritten;
			if(!WriteFile(_file, src, static_cast<DWORD>(numBytes), &bytesWritten, NULL))
				return 0;
			return static_cast<LargeInt>(bytesWritten);
		}

		LargeInt OSFileStream::ReadBytes(void *dest, LargeInt numBytes)
		{
			DWORD bytesRead;
			if(!ReadFile(_file, dest, static_cast<DWORD>(numBytes), &bytesRead, NULL))
				return 0;
			return static_cast<LargeInt>(bytesRead);
		}

		void OSFileStream::Seek(LargeInt offset, SeekType seekType)
		{
			DWORD dwSeek;

			switch(seekType)
			{
			case IFileStream::SEEK_Start:	dwSeek = FILE_BEGIN; break;
			case IFileStream::SEEK_Current:	dwSeek = FILE_CURRENT; break;
			case IFileStream::SEEK_End:		dwSeek = FILE_END; break;
			};

			if(sizeof(offset) == sizeof(LONG))
				SetFilePointer(_file, static_cast<LONG>(offset), NULL, dwSeek);
			else
			{
				Int64 offset64 = static_cast<Int64>(offset);
				LONG offsetLow = static_cast<LONG>(offset);
				LONG offsetHigh = static_cast<LONG>(offset64 >> (sizeof(LONG) * 8));
				SetFilePointer(_file, offsetLow, &offsetHigh, FILE_BEGIN);
			}
		}

		LargeInt OSFileStream::Tell()
		{
			return static_cast<LargeInt>(SetFilePointer(_file, 0, NULL, FILE_CURRENT));
		}

		void OSFileStream::Close()
		{
			if(_file)
				CloseHandle(_file);

			_ownerSys->UnlinkStream(this);

			this->~OSFileStream();
			_alloc.Free(this);
		}

		/*
	struct CMyFileSystem : public IO::IFileSystem
	{
		IO::IFileStream *Open(const Char *path, bool write)
		{
			Char xpath[Security::MAX_ZONEPOLICY_PATH + 20];
			wcscpy(xpath, RDX_STATIC_STRING("rdxclasses\\"));
			wcscat(xpath, path);
		
			FILE *f = _wfopen(xpath, write ? L"wb" : L"rb");
			if(!f)
				return NULL;
			CFileStream *fs = new CFileStream();
			fs->f = f;

			return fs;
		}

		IO::IFileScanState *ScanDirectory(OperationContext *ctx, IObjectManager *objm, const Char *directory, const Char *wildcard, const Char *vext)
		{
			return new CMyFileScanState(directory, wildcard, vext);
		}
	};
	*/
	}
}